package fr.emse.tatiana.replayable.analysis;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.MultiPageEditorPart;

import fr.emse.tatiana.remotecontrol.RemoteControl;
import fr.emse.tatiana.replayable.Anchor;
import fr.emse.tatiana.replayable.IArtefactEditor;
import fr.emse.tatiana.replayable.IGraphicalEditor;
import fr.emse.tatiana.replayable.IReplayableModel;
import fr.emse.tatiana.replayable.SynchronisationEvent;
import fr.emse.tatiana.replayable.analysis.graph.EventGraphPart;
import fr.emse.tatiana.graphical.DisplayRule;

public class GraphAnalysisEditor extends MultiPageEditorPart implements ISelectionChangedListener, IAnalysisEditor, IGraphicalEditor {

	public static final String ID = "fr.emse.tatiana.GraphAnalysisEditor";
	public GraphAnalysis model;
	private RemoteControl remote;
	private Vector<IAnalysis> analyses;
	private GraphEditor ge;
	private List<DisplayRule> rules;
	private IEditorInput input;



	public GraphAnalysisEditor() {
		analyses = new Vector<IAnalysis>();
	}


	public GraphicalViewer getViewer() {
		return ge.getViewer();
	}	
	
	protected void createGraphicalViewer() {

		try {
			ge = new GraphEditor(model, rules) {
				@Override
				public void selectionChanged(IWorkbenchPart part, ISelection selection) {
					// won't be the active editor as will be a "sub-editor"
					if (GraphAnalysisEditor.this.equals(getSite().getPage().getActiveEditor()))
						updateActions(getSelectionActions());
				}
			};
			int index = addPage(ge,input);
			setPageText(index, "Replayable");
		} catch (PartInitException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void analysisEventOccurred(AnalysisEvent evt) {
		switch(evt.getType()) {
		case AnalysisEvent.LINK_CHANGE:
		case AnalysisEvent.ELEMENT_CHANGE:
		case AnalysisEvent.ELEMENT_ADD:
		case AnalysisEvent.ELEMENT_REMOVE:
			firePropertyChange(PROP_DIRTY);
			break;
		case AnalysisEvent.TITLE_CHANGE:
			firePropertyChange(PROP_TITLE);
			firePropertyChange(PROP_DIRTY);
			break;
		}
		ge.refresh(); //TODO make all this a bit more detailed
		facetviewer.refresh();
		rulesviewer.refresh();
	}

	public IAnalysis getAnalysis() {
		return model;
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
		// TODO z later use monitor
		if (!model.hasAnalysisFile()) {
			doSaveAs();
		} else {
			model.doSave();
			firePropertyChange(PROP_DIRTY);
		}
	}

	@Override
	public void doSaveAs() {
		InputDialog dialog = new InputDialog(this.getSite().getShell(), 
				"Save analysis as...", 
				"Name of new analysis:", null, null); 
		if (dialog.open() != Window.OK)
			return;
		model.doSave(dialog.getValue());
		setPartName(model.getName());
		firePropertyChange(PROP_DIRTY);
		firePropertyChange(PROP_TITLE);
	}

	@Override
	public void dispose() {
		IEditorReference[] editors = this.getSite().getWorkbenchWindow().getActivePage().getEditorReferences();
		for (int i=0; i<editors.length; i++) {
			IEditorPart ed = editors[i].getEditor(false);
			if (ed instanceof IArtefactEditor) {
				((IArtefactEditor) ed).removeAnalysis(this.model);
			}
		}
		for (IAnalysis a: analyses) {
			a.removeAnalysisEventListener(this);
		}
		model.removeAnalysisEventListener(this);
		if (remote != null) remote.removeReplayer(this);
		super.dispose();
	}

	@Override
	public void init(IEditorSite site, IEditorInput input)
	throws PartInitException {
		this.input = input;
		try {
			this.model = (GraphAnalysis)((GraphAnalysisInput) input).getModel();
			this.model.setAnalyses(analyses);
			this.rules = model.getRules();
		} catch (Exception e) {
			throw new PartInitException("Not a graph analysis");
		}
		setInput(input);
		setSite(site);
		setPartName(input.getName());

		IEditorReference[] editors = this.getSite().getWorkbenchWindow().getActivePage().getEditorReferences();
		for (int i=0; i<editors.length; i++) {
			IEditorPart ed = editors[i].getEditor(false);
			if (ed instanceof IArtefactEditor) {
				((IArtefactEditor) ed).addAnalysis(this.model);
			}
			if (ed instanceof IAnalysisEditor) {
				addAnalysis(((IAnalysisEditor) ed).getAnalysis());
			}
		}

		model.addAnalysisEventListener(this);
		super.init(site,input);

	}

	@Override
	public boolean isDirty() {
		return !model.isSaved();
	}

	@Override
	public boolean isSaveAsAllowed() {
		return true;
	}

	public void removeAnalysis(IAnalysis a) {
		if (analyses.contains(a)) {
			analyses.remove(a);
			a.removeAnalysisEventListener(this);
			ge.refresh();
			facetviewer.refresh();
			rulesviewer.refresh();
		}
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#addAnalysis(fr.emse.tatiana.replayable.analysis.Analysis)
	 */
	public void addAnalysis(IAnalysis a) {
		if (a == this.model) {
			return;
		}
		analyses.add(a);
		a.addAnalysisEventListener(this);
//		if (model != null) { TODO update things correctly
//			Object[] rows = model.getAnalysedEvents(a);
//			updateViewer(rows);
//		}
		if (ge!=null) ge.refresh();
		if (facetviewer!=null) facetviewer.refresh();
		if (rulesviewer!=null) rulesviewer.refresh();
	}

	public void setRemote(RemoteControl remote) {
		this.remote = remote;
		getViewer().addSelectionChangedListener(this);
	}

	public List<Long> getEvents() {
		return model.getEventTimestamps();
	}

	public void end() {
		this.remote = null;
		try {
			getViewer().removeSelectionChangedListener(this);
		} catch (Exception e) {
			System.out.println("OK,got told to remove selection when had already");
		}
	}

	public void goTo(SynchronisationEvent event) {

		if (this == event.getSource()) {
			return;
		}
		Long time = event.getNow();

		// first set selection to now (if there is one)

		if (!event.isIdsynch()) {
			StructuredSelection now = StructuredSelection.EMPTY;
			List<Object> elems = model.currentEvents(event);
			StructuredSelection sel = StructuredSelection.EMPTY;
			if (elems.size() > 0) sel = new StructuredSelection(elems);
			selectEvents(sel, true, false); // select the current ones
		} else {
			if (event.getAnchors().size() > 0) {
				List<Object> anchors = model.anchoredEvents(event);
				StructuredSelection sel = StructuredSelection.EMPTY;
				if (anchors.size() > 0) sel = new StructuredSelection(anchors);
				selectEvents(sel, true, false);
			}
		}

	}

	private void selectEvents(final StructuredSelection selection, final boolean scroll, final boolean propagate) {
		final GraphAnalysisEditor self = this;
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				try {
					if (!propagate) getViewer().removeSelectionChangedListener(self);


					ge.setSelection(selection);

				} finally {
					if (!propagate) getViewer().addSelectionChangedListener(self);
				}
			}
		});

	}

	public void mark(long time) {
		// still don't know what this is supposed to do :p

	}

	public void selectionChanged(SelectionChangedEvent event) {
		StructuredSelection sel = (StructuredSelection)event.getSelection();
		if (sel.isEmpty()) return;
		List<Object> sellist = sel.toList();
		boolean go = true;
		Long first = null;
		Long last = null;
		for (Object o: sellist) {
			Object e = getAnchorFromSelection(o);
			if (model.isTimeKnown(e)) {
				if (first==null || model.getBeginTime(e)<first) first = model.getBeginTime(e);
				if (last==null || model.getLastTime(e)>last) last = model.getLastTime(e);
			}
		}
		System.err.println("RTD to: "+first);
		SynchronisationEvent se = (first==null) ? new SynchronisationEvent(this) : new SynchronisationEvent(this,first, first, last-first);
		for (Object o : sellist) {
			if (o instanceof EventGraphPart) {
				go = true;
				se.addAnchor(getAnchorFromSelection(o));
			} else {
				go = false;
			}
		}
		if (go) remote.goTo(se);

	}

	private Anchor getAnchorFromSelection(Object o) {
		return ((EventGraphPart) o).getAnchor();
	}


	@Override
	protected void createPages() {
		createGraphicalViewer();
		createRulesEditor();
	}
	
	private TableViewer rulesviewer;
	private String[] rulecolumns = new String[] { "Facet", "Facet Value",
			"Property", "Property Value" };
	private Button delete;
	private Button create;
	private DisplayRule currentrule;
	protected String facetname;
	protected String facetvalue;
	protected String property;
	protected String propertyvalue;
	protected ComboViewer facetvalueviewer;
	protected ComboViewer facetviewer;

	protected void createRulesEditor() {
		Composite c = new Composite(getContainer(), SWT.NONE);

		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 4;
		gridLayout.verticalSpacing = 10;
		gridLayout.marginHeight = 0;
		gridLayout.marginWidth = 0;
		c.setLayout(gridLayout);

		rulesviewer = new TableViewer(c, SWT.H_SCROLL | SWT.V_SCROLL
				| SWT.BORDER | SWT.FULL_SELECTION);
		Table table = rulesviewer.getTable();
		GridData griddata = new GridData(GridData.FILL, GridData.FILL, true,
				true);
		griddata.horizontalSpan = 4;
		table.setLayoutData(griddata);
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		for (int i = 0; i < rulecolumns.length; i++) {
			TableColumn column = new TableColumn(table, SWT.LEFT);
			column.setText(rulecolumns[i]);
			column.pack();
		}
		rulesviewer.setColumnProperties(rulecolumns);

		rulesviewer.setContentProvider(new IStructuredContentProvider() {

			public Object[] getElements(Object inputElement) {
				return rules.toArray();
			}

			public void dispose() {

			}

			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
				// pretend we never change the input;
			}

		});
		rulesviewer.setLabelProvider(new RulesLabelProvider());

		rulesviewer
		.addSelectionChangedListener(new ISelectionChangedListener() {

			public void selectionChanged(SelectionChangedEvent event) {
				StructuredSelection sel = (StructuredSelection) rulesviewer
				.getSelection();
				currentrule = (sel.isEmpty()) ? null
						: (DisplayRule) sel.getFirstElement();
				delete.setEnabled(!sel.isEmpty());
			}

		});

		rulesviewer.setInput(rules);

		new Label(c, SWT.NONE).setText(rulecolumns[0]);
		new Label(c, SWT.NONE).setText(rulecolumns[1]);
		new Label(c, SWT.NONE).setText(rulecolumns[2]);
		new Label(c, SWT.NONE).setText(rulecolumns[3]);

		facetviewer = new ComboViewer(c);
		facetviewer.setContentProvider(new IStructuredContentProvider() {

			public Object[] getElements(Object inputElement) {
				if (inputElement == null) {
					return new Object[] {};
				}
				IReplayableModel model = (IReplayableModel) inputElement;
				List<String> ret = new Vector<String>();
				ret.add("Any");
				for (int i = 0; i < model.getFacetCount(); i++) {
					ret.add(model.getFacetName(i));
				}
				return ret.toArray();
			}

			public void dispose() {
			}

			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
			}

		});

		facetviewer
		.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				String elem = (String) ((IStructuredSelection) event
						.getSelection()).getFirstElement();
				if ("Any".equals(elem)) {
					facetname = null;
				} else {
					facetname = elem;
				}
				facetvalueviewer.refresh();
			}
		});

		facetviewer.setInput(model);

		facetvalueviewer = new ComboViewer(c);

		facetvalueviewer.setContentProvider(new IStructuredContentProvider() {

			public Object[] getElements(Object inputElement) {
				if (inputElement == null) {
					return new Object[] {};
				}
				IReplayableModel model = (IReplayableModel) inputElement;
				Set<String> ret = new HashSet<String>();
				for (int e = 0; e < model.getEventCount(); e++) {
					Object event = model.getEvent(e);
					if (facetname == null) {
						for (int i = 0; i < model.getFacetCount(); i++) {
							String s = model.getFacetText(event, i);
							ret.add((s != null && s.length() > 50) ? s
									.substring(0, 50)
									+ "..." : s);
						} // TODO don't do this here, do it in a label provider
					} else {
						String s = model.getFacetText(event, model.getFacetCol(facetname));
						ret.add((s != null && s.length() > 50) ? s.substring(0,
								50)
								+ "..." : s);
					}
				}
				List<String> l = new LinkedList<String>(ret);
				if (l.contains(null)) {
					l.remove(null);
				}
				java.util.Collections.sort(l);
				l.add(0, "Any");
				return l.toArray();
			}

			public void dispose() {
			}

			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
			}

		});

		facetvalueviewer
		.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				String elem = (String) ((IStructuredSelection) event
						.getSelection()).getFirstElement();
				if ("Any".equals(elem)) {
					facetvalue = null;
				} else {
					facetvalue = elem;
				}
			}
		});

		facetvalueviewer.setInput(model);

		ComboViewer propertyviewer = new ComboViewer(c);
		propertyviewer.setContentProvider(new IStructuredContentProvider() {

			public Object[] getElements(Object inputElement) {
				return DisplayRule.properties;
			}

			public void dispose() {
			}

			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
			}

		});

		propertyviewer
		.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				String elem = (String) ((IStructuredSelection) event
						.getSelection()).getFirstElement();
				property = elem;
			}
		});

		propertyviewer.setInput(new Object());

		Text propertyvaluew = new Text(c, SWT.BORDER);

		propertyvaluew.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				propertyvalue = ((Text) e.getSource()).getText();

			}
		});

		create = new Button(c, SWT.PUSH);
		create.setText("Create Rule");
		create.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				// doesn't happen
			}

			public void widgetSelected(SelectionEvent e) {
				createRule();
			}
		});

		delete = new Button(c, SWT.PUSH);
		delete.setText("Delete Selected Rule");
		delete.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				// doesn't happen
			}

			public void widgetSelected(SelectionEvent e) {
				deleteRule();
			}
		});
		delete.setEnabled(false);

		int index = addPage(c);
		setPageText(index, "Visualisation Rules");
	}

	protected void deleteRule() {
		if (rules.contains(currentrule))
			rules.remove(currentrule);
		model.touchRules();
		firePropertyChange(PROP_DIRTY);
		rulesviewer.refresh();
		ge.refresh();
	}

	protected void createRule() {
		rules.add(new DisplayRule(facetname, facetvalue, property, propertyvalue));
		model.touchRules();
		firePropertyChange(PROP_DIRTY);
		rulesviewer.refresh();
		ge.refresh();
	}

	private class RulesLabelProvider extends LabelProvider implements
	ITableLabelProvider {

		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		public String getColumnText(Object element, int columnIndex) {
			DisplayRule rule = (DisplayRule) element;
			switch (columnIndex) {
			case 0:
				try {
					return (rule.getFacet() == null) ? "Any" : rule.getFacet();
				} catch (Exception e) {
					return "facet "+rule.getFacet(); // probably not needed
				}
			case 1:
				return (rule.getFacetvalue() == null) ? "Any" : rule
						.getFacetvalue();
			case 2:
				return (rule.getProperty() == null) ? "Any" : rule
						.getProperty();
			case 3:
			default:
				return (rule.getPropertyvalue() == null) ? "$"
						+ getColumnText(element, 0) : rule.getPropertyvalue()
						.toString();
			}
		}

	}


}
